<?php
class Sudoku {
	
	public $policka = array();
	public $oblasti = array(); 
	public $radky = array();
	public $sloupce = array();
	public $zaloha;

	function getOblasti() {
		return $this->oblasti;
	}
	function getRadky() {
		return $this->radky;
	}
	function getSloupce() {
		return $this->sloupce;
	}
	function getPolicka() {
		return $this->policka;
	}
	
	function __construct() {
		$id = 1;
		for ($i = 1; $i <= 9; $i++) {
			for ($j = 1; $j <= 9; $j++) {
				$this->policka[] = new Policko($i,$j, $id);
				$id++;
			}
		}
		
		for ($i = 1; $i <= 9; $i++) {
			$this->oblasti[] = new Oblast($i, $this->policka);
		}
		
		for ($i = 1; $i <= 9; $i++) {
			$this->radky[] = new Radek($i, $this->policka);
		}
		
		for ($i = 1; $i <= 9; $i++) {
			$this->sloupce[] = new Sloupec($i, $this->policka);
		}	
	}
	
	/**
	 * Nastaví číslo políčku
	 *
	 * @param id políčka
	 * @param požadované číslo
	 */
	function nastav($id,$cislo) {
		if(!empty($cislo)){
			--$id;
			$this->policka[$id]->setCislo($cislo);
		}
	}

	/**
	 * Zjistí, kolik políček bylo vyplněno
	 * @return int počet políček
	 */
	function ZjistiPocetVylustenych() {
		$pocet = 0;
		foreach ($this->policka as $policko) {
			if($policko->getCislo() != null){
				++$pocet;
			}
		}
		return $pocet;
	}
	
	/**
	 *	Zjistí, jestli je sudoku validní
	 *	@return bool 
	 */
	function JeValidni() {/*
		foreach (self::$policka as $policko) {
			if (!$policko->getStav() and (($policko->getCislo() < 1) or ($policko->getCislo() > 9))) {
				return false;
			}
		}*/
		foreach ($this->radky as $radek) {
			$radek->setObsazeno();
			$cisla = array(1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0);		
			foreach ($radek->getObsazeno() as $key => $value) {
				
				++$cisla[$value];
				if ($cisla[$value]>1) {
					return false;
				}
			}
		}
		foreach ($this->sloupce as $sloupec) {
			$sloupec->setObsazeno();
			$sloupec->setMoznosti();
			$cisla = array(1=>0,0,0,0,0,0,0,0,0);	
			foreach ($sloupec->getObsazeno() as $value) {
				if ($cisla[$value]>1) {
					return false;
				}
				++$cisla[$value];
			}
		}
		foreach ($this->oblasti as $oblast) {
			$oblast->setObsazeno();
			$oblast->setMoznosti();
			$cisla = array(1=>0,0,0,0,0,0,0,0,0);	
			foreach ($oblast->getObsazeno() as $value) {
				if ($cisla[$value]>1) {
					return false;
				}
				++$cisla[$value];
			}
		}
		return true;
	}

	/**
	 * Metoda, která vyluští sudoku
	 * Nejprve zavolá metodu, která se pokusí sudoku vyřešit základnímy postupy.
	 * Pokud se nepodaří vyřešit celé sudoku, tak najde políčko, které má pouze dvě možnosti.
	 * Dále si zazálohuje aktuální stav sudoku a do nalezeného políčka vloží první z možností.
	 * Opět se pokusí sudoku vyřešit základnímy postupy. Pokud ani teď nejsou vyluštěna všechna,
	 * je jasné, že tip byl špatný. Proto se obnoví stav sudoku před tipnutím čísla. Do přislušného
	 * políčka se vloží druhá z možností a sudoku se opět dořeší základnímy postupy.
	 */
	function Lusti() {
		$ok = 1;
		// Doupravit cyklus, aby se zastavil v případě, že na sudoku neproběhli žádné změny
		while ($ok < 4){
			$this->ZakladniPostupy();
			if ($this->ZjistiPocetVylustenych()<81) {
				$this->SlozitePostupy();
			}
			++$ok;
		}
		
	}
	
	function SlozitePostupy() {
		$policko = $this->NajdiVhodneCislo();
		if($policko == false){
			return;
		}
		$id = $policko - 1;
		$moznosti = $this->policka[$id]->getMoznosti();
		rsort($moznosti);
		$pocet = count($moznosti);
		if($pocet>2){
			echo "<p>K řešení tohoto sudoku se použila experimentální funkce, která může způsobit chyby v řešení.</p>";
		}
		
		$this->Zalohuj();
		
		$this->nastav($policko, $moznosti[0]);
		$this->ZakladniPostupy();
		
		if ($this->ZjistiPocetVylustenych() < 81 and $this->JeValidni() == false) {
			$this->Obnov();
			$this->nastav($policko, $moznosti[1]);
			$this->ZakladniPostupy();
		}
		
		if ($this->ZjistiPocetVylustenych() < 81 and $pocet == 3/* and $this->JeValidni() == false*/) {
			$this->Obnov();
			$this->nastav($policko, $moznosti[2]);
			$this->ZakladniPostupy();
		}
		
		if ($this->ZjistiPocetVylustenych() < 81 and $pocet == 4/* and $this->JeValidni() == false*/) {
			$this->Obnov();
			$this->nastav($policko, $moznosti[3]);
			$this->ZakladniPostupy();
		}
	}

	/**
	 * Metoda, která vyzkouší na sudoku základní postupy luštění.
	 * To znamená, že hledá políčko s jedinou možností a políčko,
	 * jehož jedna možnost je jako jediná v oblasti/řádku/sloupci.
	 * Metoda obsahuje "inteligentní" cyklus, který se sám zastaví,
	 * pokud byli vyluštěny všechny políčka nebo pokud při poslední
	 * iteraci nebylo vyluštěno ani jedno políčko.
	 */
	function ZakladniPostupy() {
		$ok = true;
		while ($ok){
			$ok = false;
			
			if($this->JeValidni()){
				foreach ($this->getOblasti() as $value) {
					$value->setObsazeno();
					$value->setMoznosti();
					$zmena = $value->NajdiJedineMoznosti();
					if($zmena){
						$ok = true;
					}
					$zmena = $value->PolickoSJedinouMoznosti();
					if($zmena){
						$ok = true;
					}
				}
				foreach ($this->getRadky() as $value) {
					$value->setObsazeno();
					$value->setMoznosti();
					$zmena = $value->NajdiJedineMoznosti();
					if($zmena){
						$ok = true;
					}
					$zmena = $value->PolickoSJedinouMoznosti();
					if($zmena){
						$ok = true;
					}
				}
				foreach ($this->getSloupce() as $value) {
					$value->setObsazeno();
					$value->setMoznosti();
					$zmena = $value->NajdiJedineMoznosti();
					if($zmena){
						$ok = true;
					}
					$zmena = $value->PolickoSJedinouMoznosti();
					if($zmena){
						$ok = true;
					}
				}
			}else{
				return;
			}
			if($this->ZjistiPocetVylustenych()<81 and $ok){
				$ok = true;
			}else{
				$ok = false;
			}
		}
	}
	
	
	function NajdiVhodneCislo($pocet = 2) {
		foreach ($this->policka as $value) {
			$moznosti = $value->getMoznosti();
			if (count($moznosti) == $pocet and $value->getStav()) {
				return $value->getId();
			}
		}
		$this->NajdiVhodneCislo(++$pocet);
	}
	
	function Zalohuj() {
		$this->zaloha = null;
		$this->zaloha = new Zaloha($this->policka, $this->radky, $this->sloupce, $this->oblasti);
	}
	
	function Obnov() {
		$this->policka = array();
		$this->oblasti = array();
		$this->radky = array();
		$this->sloupce = array();
		
		foreach ($this->zaloha->policka as $key => $value) {
			$this->policka[$key] = clone $value;
		}
		foreach ($this->zaloha->radky as $key => $value) {
			$this->radky[$key] = clone $value;
			$this->radky[$key]->setPolicka($this->policka);
		}
		foreach ($this->zaloha->sloupce as $key => $value) {
			$this->sloupce[$key] = clone $value;
			$this->sloupce[$key]->setPolicka($this->policka);
		}
		foreach ($this->zaloha->oblasti as $key => $value) {
			$this->oblasti[$key] = clone $value;
			$this->oblasti[$key]->setPolicka($this->policka);
		}
	}
}
?>